package com.example.demo.Util;

import com.example.demo.model.Answer;
import com.example.demo.model.Question;
import org.springframework.util.StringUtils;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/**
 * Created by xiebang
 * Description 每次的编译运行这个过程是一个 Task
 * 核心方法就是
 * 参数：
 * 返回值：
 *
 * User:小谢
 * Date:2022-09-19
 * Time:12:00
 */
public class Task {
//    约定一组常量来约定临时文件的名字
//    文件所在的目录
    public static String WORK_DIR =null;
//    约定代码的类名
    public static String CLASS = null;
//    代码的文件名
    public static String CODE =null;
//    存放编译错误的文件名
    public static String COMPILE_ERROR = null;
//    约定存放运行时的标准输出的文件名
    public static String STDOUT = null;
//    存放标准错误的文件名
    public static String STDERR = null;

    public Task(){
        WORK_DIR = "./tmp/" + UUID.randomUUID().toString()+"/";
        CLASS = "Solution";
        CODE =WORK_DIR + "Solution.java";
        COMPILE_ERROR = WORK_DIR + "compileError.txt";
        STDOUT = WORK_DIR + "stdout.txt";

        STDERR = WORK_DIR + "stderr.txt";
    }
    /**
     * compileAndRun 编译+ 运行
     * @param question 是要编译的java代码
     * @return 是编译运行的结果
     */
    public Answer compileAndRun(Question question){
        Answer answer = new Answer();
//        准备好用来存放临时文件的目录
        File workDir = new File(WORK_DIR);
        if (!workDir.exists()){
//            创建多级目录
            workDir.mkdirs();
        }
        if (!checkCodeSafe(question.getCode())){
            System.out.println("用户提交了不安全的代码！");
            answer.setError(3);
            answer.setReason("您提交的代码可能危害服务器，禁止运行！");
            return answer;
        }
//        1.把question 中的code 写入到一个 Solution.java 文件中（约定这个文件名为 Solution.java）
        FileUtil.writeFile(CODE,question.getCode());
//        2.创建子进程，调用 javac 进行编译，因此需要一个 .java文件中
//        构造编译命令 javac + encoding utf8 + 源文件名(绝对路径) + -d(指定编译生成的文件路径) + WORk_DIR
        String compileCmd = String.format("javac -encoding utf8 %s -d %s",CODE,WORK_DIR);
//        System.out.println("编译命令： " + compileCmd);
        int exitCode =  CommandUtil.run(compileCmd,null,COMPILE_ERROR);
        String compileError = FileUtil.readFile(COMPILE_ERROR);
//         不为空就是编译出错
//        如果编译出错，javac 就会把这个错误信息给写入到 stderr 里，就可以用一个专门的文件来保存 compileError.txt
        if (StringUtils.hasLength(compileError)){
            System.out.println("编译出错");
            // 编译出错直接返回Answer,
            answer.setError(1);
            answer.setReason(compileError);
            return answer;
        }


//        3.创建子进程，调用java 命令并执行
        String runCmd = String.format("java -classpath %s %s",WORK_DIR,CLASS);
//        System.out.println("运行命令" + runCmd);
        CommandUtil.run(runCmd,STDOUT,STDERR);

        String stderr = FileUtil.readFile(STDERR);
        if (StringUtils.hasLength(stderr)){
            System.out.println("运行出错");
            answer.setError(2);
            answer.setStderr(stderr);
            return answer;
        }

        String stdout = FileUtil.readFile(STDOUT);
        answer.setError(0);
        answer.setStdout(stdout);
//        如果运行出错，把java 子进程的标准输出和标准错误获取到 stdout.txt stderr.txt;
//        4.父进程获取到刚才编译执行的结果 并打包成 Answer对象

        return answer;
    }

    private boolean checkCodeSafe(String code) {
        List<String> blackList = new ArrayList<>();
        blackList.add("Runtime");
        blackList.add("exec");
        blackList.add("java.io");
        blackList.add("java.net");

        for (String target : blackList){
            int pos = code.indexOf(target);
            if (pos >= 0){
                return false;
            }
        }
        return true;
    }

    //
public static void main(String[] args) {
    Task task = new Task();
    Question question = new Question();
    question.setCode("public class Solution {\n" +
            "    public static void main(String[] args) {\n" +
            "        System.out.println(\"hello world\");\n" +
            "    }\n" +
            "}");
    Answer answer = task.compileAndRun(question);
    System.out.println(answer);
}
}
